
{***********************************************************}
{                                                           }
{      Luxena Database Library                              }
{                                                           }
{      MacroSQL Support Classes                             }
{                                                           }
{      Copyright (c) 2001-2008 Luxena Software Company      }
{      http://www.luxena.com                                }
{      e-mail: support@luxena.com                           }
{                                                           }
{***********************************************************}

unit LXMacroSQL;

{$I lx.inc}

interface

uses Classes, SysUtils;

type

{ MacroSQL elements }

  ILXMacroSQLParser =  interface;
  ILXMacroSQLVisitor = interface;

  TLXMacroSQLElement = class
  public
    procedure Accept(const Visitor: ILXMacroSQLVisitor); virtual; abstract;
  end;

  TLXMacroSQLText = class(TLXMacroSQLElement)
  private
    FText: string;
  public
    constructor Create(const Text: string);
    procedure Accept(const Visitor: ILXMacroSQLVisitor); override;
    property Text: string read FText;
  end;

  TLXMacroSQLParameter = class(TLXMacroSQLText)
  public
    procedure Accept(const Visitor: ILXMacroSQLVisitor); override;
  end;

  TLXMacroSQLMacro = class(TLXMacroSQLText)
  public
    procedure Accept(const Visitor: ILXMacroSQLVisitor); override;
  end;

  TLXMacroSQLLogicalExpression = class(TLXMacroSQLElement)
  end;

  TLXMacroSQLConditional = class(TLXMacroSQLElement)
  private
    FCondition: TLXMacroSQLLogicalExpression;
    FThenElement: TLXMacroSQLElement;
    FElseElement: TLXMacroSQLElement;
  public
    constructor Create(Condition: TLXMacroSQLLogicalExpression;
      ThenElement, ElseElement: TLXMacroSQLElement);
    destructor Destroy; override;
    procedure Accept(const Visitor: ILXMacroSQLVisitor); override;
    property Condition: TLXMacroSQLLogicalExpression read FCondition;
    property ThenElement: TLXMacroSQLElement read FThenElement;
    property ElseElement: TLXMacroSQLElement read FElseElement;
  end;

  TLXMacroSQLError = class(TLXMacroSQLText)
  private
    FLine: Integer;
    FColumn: Integer;
  public
    constructor Create(const Text: string; Line, Column: Integer);
    procedure Accept(const Visitor: ILXMacroSQLVisitor); override;
    property Line: Integer read FLine;
    property Column: Integer read FColumn;
  end;

  TLXMacroSQLElements = class(TLXMacroSQLElement)
  private
    FElements: TList;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Accept(const Visitor: ILXMacroSQLVisitor); override;
    function Add(Element: TLXMacroSQLElement): TLXMacroSQLElements;
  end;

  TLXMacroSQLLogicalIdentifier = class(TLXMacroSQLText)
  public
    procedure Accept(const Visitor: ILXMacroSQLVisitor); override;
  end;

  TLXMacroSQLLogicalExists = class(TLXMacroSQLLogicalExpression)
  private
    FIdentifier: TLXMacroSQLLogicalIdentifier;
  public
    constructor Create(Identifier: TLXMacroSQLLogicalIdentifier);
    destructor Destroy; override;
    procedure Accept(const Visitor: ILXMacroSQLVisitor); override;
    property Identifier: TLXMacroSQLLogicalIdentifier read FIdentifier;
  end;

  TLXMacroSQLLogicalCompare = class(TLXMacroSQLLogicalExpression)
  private
    FLeftOperand: TLXMacroSQLLogicalIdentifier;
    FRightOperand: TLXMacroSQLLogicalIdentifier;
  public
    constructor Create(LeftOperand, RightOperand: TLXMacroSQLLogicalIdentifier);
    destructor Destroy; override;
    property LeftOperand: TLXMacroSQLLogicalIdentifier read FLeftOperand;
    property RightOperand: TLXMacroSQLLogicalIdentifier read FRightOperand;
  end;

  TLXMacroSQLLogicalEQUAL = class(TLXMacroSQLLogicalCompare)
  public
    procedure Accept(const Visitor: ILXMacroSQLVisitor); override;
  end;

  TLXMacroSQLLogicalNOTEQUAL = class(TLXMacroSQLLogicalCompare)
  public
    procedure Accept(const Visitor: ILXMacroSQLVisitor); override;
  end;

  TLXMacroSQLLogicalNOT = class(TLXMacroSQLLogicalExpression)
  private
    FOperand: TLXMacroSQLLogicalExpression;
  public
    constructor Create(Operand: TLXMacroSQLLogicalExpression);
    destructor Destroy; override;
    procedure Accept(const Visitor: ILXMacroSQLVisitor); override;
    property Operand: TLXMacroSQLLogicalExpression read FOperand;
  end;

  TLXMacroSQLLogicalBinaryOperator = class(TLXMacroSQLLogicalExpression)
  private
    FLeftOperand: TLXMacroSQLLogicalExpression;
    FRightOperand: TLXMacroSQLLogicalExpression;
  public
    constructor Create(LeftOperand, RightOperand: TLXMacroSQLLogicalExpression);
    destructor Destroy; override;
    property LeftOperand: TLXMacroSQLLogicalExpression read FLeftOperand;
    property RightOperand: TLXMacroSQLLogicalExpression read FRightOperand;
  end;

  TLXMacroSQLLogicalAND = class(TLXMacroSQLLogicalBinaryOperator)
  public
    procedure Accept(const Visitor: ILXMacroSQLVisitor); override;
  end;

  TLXMacroSQLLogicalOR = class(TLXMacroSQLLogicalBinaryOperator)
  public
    procedure Accept(const Visitor: ILXMacroSQLVisitor); override;
  end;

  
{ ILXMacroSQLParser }

  ILXMacroSQLParser = interface
    ['{413E1BFD-D33F-4CCC-93BD-8415C71736A7}']
    function Parse(const SQL: string): TLXMacroSQLElement;
  end;


{ ILXMacroSQLVisitor }

  ILXMacroSQLVisitor = interface
    ['{F2CA9E7E-E6C7-4C0E-B1F6-A10CF5D35BF6}']
    procedure VisitText(Element: TLXMacroSQLText);
    procedure VisitParameter(Element: TLXMacroSQLParameter);
    procedure VisitMacro(Element: TLXMacroSQLMacro);
    procedure VisitConditional(Element: TLXMacroSQLConditional);
    procedure VisitError(Element: TLXMacroSQLError);
    procedure VisitLogicalIdentifier(Element: TLXMacroSQLLogicalIdentifier);
    procedure VisitLogicalExists(Element: TLXMacroSQLLogicalExists);
    procedure VisitLogicalEQUAL(Element: TLXMacroSQLLogicalEQUAL);
    procedure VisitLogicalNOTEQUAL(Element: TLXMacroSQLLogicalNOTEQUAL);
    procedure VisitLogicalNOT(Element: TLXMacroSQLLogicalNOT);
    procedure VisitLogicalAND(Element: TLXMacroSQLLogicalAND);
    procedure VisitLogicalOR(Element: TLXMacroSQLLogicalOR);
  end;

  
{ TLXMacroSQLParser }

  ELXMacroSQLError = class(Exception)
  private
    FLine: Integer;
    FColumn: Integer;
    FEngineMessage: string;
  public
    constructor Create(const EngineMessage: string; Line, Column: Integer);
    property Line: Integer read FLine;
    property Column: Integer read FColumn;
    property EngineMessage: string read FEngineMessage;
  end;

  TLXMacroSQLDirective = (msdNone, msdIF, msdELSEIF, msdELSE, msdENDIF);

  TLXMacroSQLParseElementsResult = record
    Elements: TLXMacroSQLElements;
    Directive: TLXMacroSQLDirective;
  end;

  TLXMacroSQLParser = class(TInterfacedObject, ILXMacroSQLParser)
  private
    FParamChar, FMacroChar: Char;
    FSource: PChar;
    FCurrent: PChar;
    FLine: Integer;
    FColumn: Integer;
    procedure ReportError(const ErrorText: string);
    procedure IncAndCount; overload;
    procedure IncAndCount(Number: Integer); overload;
    function CompareString(const S: string): Boolean;
    procedure SkipStringLiteral;
    procedure SkipLine;
    procedure SkipMultiLineComment;
    procedure SkipBlanksAfterDirective;
    procedure SkipLogicalExpressionBlanks;
    function ParseElements: TLXMacroSQLParseElementsResult;
    function ParseDelimiter(Elements: TLXMacroSQLElements): Boolean;
    function ParseIdentifier: string;
    function ParseDirective(Elements: TLXMacroSQLElements): TLXMacroSQLDirective;
    function ParseConditionalOperator: TLXMacroSQLConditional;
    function ParseError: TLXMacroSQLError;
    function ParseLogicalExpression: TLXMacroSQLLogicalExpression;
    function ParseLogicalMultiplication: TLXMacroSQLLogicalExpression;
    function ParseLogicalPrimary: TLXMacroSQLLogicalExpression;
    function ParseLogicalSubExpression: TLXMacroSQLLogicalExpression;
    function ParseLogicalPrimitive: TLXMacroSQLLogicalExpression;
    function ParseLogicalIdentifier: TLXMacroSQLLogicalIdentifier;
  public
    constructor Create(ParamChar, MacroChar: Char);
    function Parse(const SQL: string): TLXMacroSQLElement;
  end;


{ TLXMacroSQLBuilder }

  ILXMacroSQLResult = interface
    ['{3CD1AC94-D704-4133-8E3D-4BB9821FBDDE}']
    function SQL: string;
    function ParamNames: TStrings;
  end;

  TLXMacroSQLBuilder = class(TInterfacedObject, ILXMacroSQLVisitor, ILXMacroSQLResult)
  private
    FParser: ILXMacroSQLParser;
    FMacroValues: TStrings;
    FParamNames: TStrings;
    FSQL: string;
    FStringValue: string;
    FBooleanValue: Boolean;
  public
    constructor Create(const Parser: ILXMacroSQLParser; MacroValues: TStrings);
    destructor Destroy; override;
    { ILXMacroSQLVisitor }
    procedure VisitText(Element: TLXMacroSQLText);
    procedure VisitParameter(Element: TLXMacroSQLParameter);
    procedure VisitMacro(Element: TLXMacroSQLMacro);
    procedure VisitConditional(Element: TLXMacroSQLConditional);
    procedure VisitError(Element: TLXMacroSQLError);
    procedure VisitLogicalIdentifier(Element: TLXMacroSQLLogicalIdentifier);
    procedure VisitLogicalExists(Element: TLXMacroSQLLogicalExists);
    procedure VisitLogicalEQUAL(Element: TLXMacroSQLLogicalEQUAL);
    procedure VisitLogicalNOTEQUAL(Element: TLXMacroSQLLogicalNOTEQUAL);
    procedure VisitLogicalNOT(Element: TLXMacroSQLLogicalNOT);
    procedure VisitLogicalAND(Element: TLXMacroSQLLogicalAND);
    procedure VisitLogicalOR(Element: TLXMacroSQLLogicalOR);
    { ILXMacroSQLResult }
    function SQL: string;
    function ParamNames: TStrings;
  end;


implementation

uses LXConsts, LXDBConsts;

const
  LINE_COMMENT = '--';
  MULTILINE_COMMENT_BEGIN = '/*';
  MULTILINE_COMMENT_END = '*/';
  LITERAL_DELIMITER = ['''', '"', '`'];
  BLANK_CHAR = [#9, #10, #13, ' '];
  NAME_CHAR = ['_', '0'..'9', 'A'..'Z', 'a'..'z'];
  DIRECTIVE_DELIMITER = '#';
  OPERATOR_IF = 'if';
  OPERATOR_ELSEIF = 'elseif';
  OPERATOR_ELSE = 'else';
  OPERATOR_ENDIF = 'endif';
  OPERATOR_ERROR = 'error';

  
{ TLXMacroSQLText }

constructor TLXMacroSQLText.Create(const Text: string);
begin
  inherited Create;
  FText := Text;
end;

procedure TLXMacroSQLText.Accept(const Visitor: ILXMacroSQLVisitor);
begin
  Visitor.VisitText(Self);
end;


{ TLXMacroSQLParameter }

procedure TLXMacroSQLParameter.Accept(const Visitor: ILXMacroSQLVisitor);
begin
  Visitor.VisitParameter(Self);
end;


{ TLXMacroSQLMacro }

procedure TLXMacroSQLMacro.Accept(const Visitor: ILXMacroSQLVisitor);
begin
  Visitor.VisitMacro(Self);
end;


{ TLXMacroSQLConditional }

constructor TLXMacroSQLConditional.Create(Condition: TLXMacroSQLLogicalExpression;
  ThenElement, ElseElement: TLXMacroSQLElement);
begin
  inherited Create;
  FCondition := Condition;
  FThenElement := ThenElement;
  FElseElement := ElseElement;
end;

destructor TLXMacroSQLConditional.Destroy;
begin
  FCondition.Free;
  FThenElement.Free;
  FElseElement.Free;
  inherited;
end;

procedure TLXMacroSQLConditional.Accept(const Visitor: ILXMacroSQLVisitor);
begin
  Visitor.VisitConditional(Self);
end;


{ TLXMacroSQLError }

constructor TLXMacroSQLError.Create(const Text: string; Line, Column: Integer);
begin
  inherited Create(Text);
  FLine := Line;
  FColumn := Column;
end;

procedure TLXMacroSQLError.Accept(const Visitor: ILXMacroSQLVisitor);
begin
  Visitor.VisitError(Self);
end;


{ TLXMacroSQLElements }

constructor TLXMacroSQLElements.Create;
begin
  inherited;
  FElements := TList.Create;
end;

destructor TLXMacroSQLElements.Destroy;
var
  I: Integer;
begin
  for I := 0 to FElements.Count - 1 do
    TLXMacroSQLElement(FElements[I]).Free;
  FElements.Free;
  inherited;
end;

procedure TLXMacroSQLElements.Accept(const Visitor: ILXMacroSQLVisitor);
var
  I: Integer;
begin
  for I := 0 to FElements.Count - 1 do
    TLXMacroSQLElement(FElements[I]).Accept(Visitor);
end;

function TLXMacroSQLElements.Add(Element: TLXMacroSQLElement): TLXMacroSQLElements;
begin
  FElements.Add(Element);
  Result := Self;
end;


{ TLXMacroSQLLogicalIdentifier }

procedure TLXMacroSQLLogicalIdentifier.Accept(const Visitor: ILXMacroSQLVisitor);
begin
  Visitor.VisitLogicalIdentifier(Self);
end;


{ TLXMacroSQLLogicalExists }

constructor TLXMacroSQLLogicalExists.Create(Identifier: TLXMacroSQLLogicalIdentifier);
begin
  inherited Create;
  FIdentifier := Identifier;
end;

destructor TLXMacroSQLLogicalExists.Destroy;
begin
  Identifier.Free;
  inherited;
end;

procedure TLXMacroSQLLogicalExists.Accept(const Visitor: ILXMacroSQLVisitor);
begin
  Visitor.VisitLogicalExists(Self);
end;


{ TLXMacroSQLLogicalCompare }

constructor TLXMacroSQLLogicalCompare.Create(LeftOperand,
  RightOperand: TLXMacroSQLLogicalIdentifier);
begin
  inherited Create;
  FLeftOperand := LeftOperand;
  FRightOperand := RightOperand;
end;

destructor TLXMacroSQLLogicalCompare.Destroy;
begin
  FLeftOperand.Free;
  FRightOperand.Free;
  inherited;
end;


{ TLXMacroSQLLogicalEQUAL }

procedure TLXMacroSQLLogicalEQUAL.Accept(const Visitor: ILXMacroSQLVisitor);
begin
  Visitor.VisitLogicalEQUAL(Self);
end;


{ TLXMacroSQLLogicalNOTEQUAL }

procedure TLXMacroSQLLogicalNOTEQUAL.Accept(const Visitor: ILXMacroSQLVisitor);
begin
  Visitor.VisitLogicalNOTEQUAL(Self);
end;


{ TLXMacroSQLLogicalNOT }

constructor TLXMacroSQLLogicalNOT.Create(Operand: TLXMacroSQLLogicalExpression);
begin
  inherited Create;
  FOperand := Operand;
end;

destructor TLXMacroSQLLogicalNOT.Destroy;
begin
  FOperand.Free;
  inherited;
end;

procedure TLXMacroSQLLogicalNOT.Accept(const Visitor: ILXMacroSQLVisitor);
begin
  Visitor.VisitLogicalNOT(Self);
end;


{ TLXMacroSQLLogicalBinaryOperator }

constructor TLXMacroSQLLogicalBinaryOperator.Create(LeftOperand,
  RightOperand: TLXMacroSQLLogicalExpression);
begin
  inherited Create;
  FLeftOperand := LeftOperand;
  FRightOperand := RightOperand;
end;

destructor TLXMacroSQLLogicalBinaryOperator.Destroy;
begin
  FLeftOperand.Free;
  FRightOperand.Free;
  inherited;
end;


{ TLXMacroSQLLogicalAND }

procedure TLXMacroSQLLogicalAND.Accept(const Visitor: ILXMacroSQLVisitor);
begin
  Visitor.VisitLogicalAND(Self);
end;


{ TLXMacroSQLLogicalOR }

procedure TLXMacroSQLLogicalOR.Accept(const Visitor: ILXMacroSQLVisitor);
begin
  Visitor.VisitLogicalOR(Self);
end;


{ ELXMacroSQLError }

constructor ELXMacroSQLError.Create(const EngineMessage: string; Line,
  Column: Integer);
begin
  inherited Create(Format('%s'+ SLineBreak + 'Line: %d, Column: %d',
    [EngineMessage, Line, Column]));
  FLine := Line;
  FColumn := Column;
  FEngineMessage := EngineMessage;
end;


{ TLXMacroSQLParser }

constructor TLXMacroSQLParser.Create(ParamChar, MacroChar: Char);
begin
  inherited Create;
  FParamChar := ParamChar;
  FMacroChar := MacroChar;
end;

function TLXMacroSQLParser.Parse(const SQL: string): TLXMacroSQLElement;
var
  ParseElementsResult: TLXMacroSQLParseElementsResult;
begin
  FSource := PChar(SQL);
  FCurrent := PChar(SQL);
  FLine := 1;
  FColumn := 1;
  ParseElementsResult := ParseElements;
  if ParseElementsResult.Directive <> msdNone then
  begin
    ParseElementsResult.Elements.Free;
    ReportError(SUnexpectedDirective);
  end;
  Result := ParseElementsResult.Elements;
end;

function Extract(P1, P2: PChar): string;
var
  C: Char;
begin
  C := P2^;
  P2^ := #0;
  Result := string(P1);
  P2^ := C;
end;

procedure TLXMacroSQLParser.ReportError(const ErrorText: string);
begin
  raise ELXMacroSQLError.Create(ErrorText, FLine, FColumn);
end;

procedure TLXMacroSQLParser.IncAndCount;
begin
  if FCurrent^ = #10 then
  begin
    Inc(FLine);
    FColumn := 1;
  end else
    if FCurrent^ <> #13 then
      Inc(FColumn);
  Inc(FCurrent);
end;

procedure TLXMacroSQLParser.IncAndCount(Number: Integer);
begin
  while Number > 0 do
  begin
    IncAndCount;
    Dec(Number);
  end;
end;

function TLXMacroSQLParser.CompareString(const S: string): Boolean;
var
  L: Integer;
begin
  L := Length(S);
  Result := AnsiStrLIComp(FCurrent, PChar(S), L) = 0;
  if Result then
    IncAndCount(L);
end;

procedure TLXMacroSQLParser.SkipStringLiteral;
var
  Quote: Char;
begin
  Quote := FCurrent^;
  IncAndCount;
  while FCurrent^ <> #0 do
  begin
    if FCurrent^ = Quote then
    begin
      IncAndCount;
      if FCurrent^ <> Quote then
        Break;
    end;
    IncAndCount;
  end;
end;

procedure TLXMacroSQLParser.SkipLine;
begin
  while (FCurrent^ <> #10) and (FCurrent^ <> #0) do IncAndCount;
  if FCurrent^ <> #0 then IncAndCount;
end;

procedure TLXMacroSQLParser.SkipMultiLineComment;
begin
  while (FCurrent^ <> #0) and not CompareString(MULTILINE_COMMENT_END) do
    IncAndCount;
end;

procedure TLXMacroSQLParser.SkipBlanksAfterDirective;
begin
  while (FCurrent^ in (BLANK_CHAR - [#10])) and (FCurrent^ <> #0) do
    IncAndCount;
  if FCurrent^ = #10 then
    IncAndCount;
  FSource := FCurrent;
end;

procedure TLXMacroSQLParser.SkipLogicalExpressionBlanks;
begin
  while (FCurrent^ in BLANK_CHAR) and (FCurrent^ <> #0) do IncAndCount;
end;

function TLXMacroSQLParser.ParseElements: TLXMacroSQLParseElementsResult;
begin
  Result.Elements := TLXMacroSQLElements.Create;
  Result.Directive := msdNone;

  while (FCurrent^ <> #0) and (Result.Directive = msdNone) do

    if FCurrent^ = FParamChar then
    begin
      if ParseDelimiter(Result.Elements) then
        Result.Elements.Add(TLXMacroSQLParameter.Create(ParseIdentifier));
    end

    else if FCurrent^ = FMacroChar then
    begin
      if ParseDelimiter(Result.Elements) then
        Result.Elements.Add(TLXMacroSQLMacro.Create(ParseIdentifier));
    end

    else if FCurrent^ = DIRECTIVE_DELIMITER then
    begin
      if ParseDelimiter(Result.Elements) then
        Result.Directive := ParseDirective(Result.Elements);
    end

    else if FCurrent^ in LITERAL_DELIMITER then
      SkipStringLiteral

    else if CompareString(LINE_COMMENT) then
      SkipLine

    else if CompareString(MULTILINE_COMMENT_BEGIN) then
      SkipMultiLineComment

    else
      IncAndCount;

  if FSource <> FCurrent then
  begin
    Result.Elements.Add(TLXMacroSQLText.Create(Extract(FSource, FCurrent)));
    FSource := FCurrent;
  end;
end;

function TLXMacroSQLParser.ParseDelimiter(Elements: TLXMacroSQLElements): Boolean;
begin
  Result := FCurrent[1] <> FCurrent[0];
  if not Result then
    IncAndCount;
  Elements.Add(TLXMacroSQLText.Create(Extract(FSource, FCurrent)));
  IncAndCount;
  FSource := FCurrent;
end;

function TLXMacroSQLParser.ParseIdentifier: string;
begin
  if FCurrent^ in LITERAL_DELIMITER then
  begin
    SkipStringLiteral;
    Result := Extract(FSource + 1, FCurrent - 1);
  end else
  begin
    while FCurrent^ in NAME_CHAR do IncAndCount;
    Result := Extract(FSource, FCurrent);
  end;
  FSource := FCurrent;
end;

function TLXMacroSQLParser.ParseDirective(Elements: TLXMacroSQLElements): TLXMacroSQLDirective;
begin
  if CompareString(OPERATOR_IF) then
  begin
    Elements.Add(ParseConditionalOperator);
    Result := msdNone;
  end else if CompareString(OPERATOR_ELSEIF) then
  begin
    SkipBlanksAfterDirective;
    Result := msdELSEIF
  end else if CompareString(OPERATOR_ELSE) then
  begin
    SkipBlanksAfterDirective;
    Result := msdELSE;
  end else if CompareString(OPERATOR_ENDIF) then
  begin
    SkipBlanksAfterDirective;
    Result := msdENDIF;
  end else if CompareString(OPERATOR_ERROR) then
  begin
    Elements.Add(ParseError);
    Result := msdNone;
  end else
    Result := msdNone;
end;

function TLXMacroSQLParser.ParseConditionalOperator: TLXMacroSQLConditional;
var
  Condition: TLXMacroSQLLogicalExpression;
  ParseElementsResult: TLXMacroSQLParseElementsResult;
  ThenElement, ElseElement: TLXMacroSQLElement;
begin
  Condition := nil;
  ThenElement := nil;
  ElseElement := nil;
  try
    Condition := ParseLogicalSubExpression;
    SkipBlanksAfterDirective;
    ParseElementsResult := ParseElements;
    ThenElement := ParseElementsResult.Elements;
    if ParseElementsResult.Directive = msdELSEIF then
      ElseElement := ParseConditionalOperator else
    begin
      if ParseElementsResult.Directive <> msdELSE then
        ElseElement := nil else
      begin
        ParseElementsResult := ParseElements;
        ElseElement := ParseElementsResult.Elements;
      end;
      if ParseElementsResult.Directive <> msdENDIF then
        ReportError(SEndIfExpected);
    end;
    Result := TLXMacroSQLConditional.Create(Condition, ThenElement, ElseElement);
  except
    Condition.Free;
    ThenElement.Free;
    ElseElement.Free;
    raise;
  end;
end;

function TLXMacroSQLParser.ParseError: TLXMacroSQLError;
var
  Line, Column: Integer;
begin
  Line := FLine;
  Column := FColumn;
  FSource := FCurrent;
  SkipLine;
  Result := TLXMacroSQLError.Create(Trim(Extract(FSource, FCurrent)), Line,
    Column);
end;

function TLXMacroSQLParser.ParseLogicalExpression: TLXMacroSQLLogicalExpression;
begin
  Result := ParseLogicalMultiplication;
  SkipLogicalExpressionBlanks;
  if CompareString('or') then
    Result := TLXMacroSQLLogicalOR.Create(Result, ParseLogicalExpression);
end;

function TLXMacroSQLParser.ParseLogicalMultiplication: TLXMacroSQLLogicalExpression;
begin
  Result := ParseLogicalPrimary;
  SkipLogicalExpressionBlanks;
  if CompareString('and') then
    Result := TLXMacroSQLLogicalAND.Create(Result, ParseLogicalMultiplication);
end;

function TLXMacroSQLParser.ParseLogicalPrimary: TLXMacroSQLLogicalExpression;
begin
  SkipLogicalExpressionBlanks;
  if FCurrent^ = '(' then
    Result := ParseLogicalSubExpression
  else if CompareString('not') then
    Result := TLXMacroSQLLogicalNOT.Create(ParseLogicalPrimary)
  else
    Result := ParseLogicalPrimitive;
end;

function TLXMacroSQLParser.ParseLogicalSubExpression: TLXMacroSQLLogicalExpression;
begin
  SkipLogicalExpressionBlanks;
  if FCurrent^ <> '(' then
    ReportError(SOpeningBracketExpected);
  IncAndCount;
  Result := ParseLogicalExpression;
  SkipLogicalExpressionBlanks;
  if FCurrent^ <> ')' then
  begin
    Result.Free;
    ReportError(SClosingBracketExpected);
  end;
  IncAndCount;
end;

function TLXMacroSQLParser.ParseLogicalPrimitive: TLXMacroSQLLogicalExpression;
var
  LeftOperand: TLXMacroSQLLogicalIdentifier;
begin
  LeftOperand := ParseLogicalIdentifier;
  SkipLogicalExpressionBlanks;
  if FCurrent^ = '=' then
  begin
    IncAndCount;
    Result := TLXMacroSQLLogicalEQUAL.Create(LeftOperand, ParseLogicalIdentifier);
  end
  else if (FCurrent[0] = '<') and (FCurrent[1] = '>') then
  begin
    IncAndCount(2);
    Result := TLXMacroSQLLogicalNOTEQUAL.Create(LeftOperand, ParseLogicalIdentifier)
  end else
    Result := TLXMacroSQLLogicalExists.Create(LeftOperand);
end;

function TLXMacroSQLParser.ParseLogicalIdentifier: TLXMacroSQLLogicalIdentifier;
var
  Identifier: string;
begin
  SkipLogicalExpressionBlanks;
  FSource := FCurrent;
  Identifier := ParseIdentifier;
  if Identifier = '' then
    ReportError(SIdentifierExpected);
  Result := TLXMacroSQLLogicalIdentifier.Create(Identifier);
end;


{ TLXMacroSQLBuilder }

constructor TLXMacroSQLBuilder.Create(const Parser: ILXMacroSQLParser;
  MacroValues: TStrings);
begin
  inherited Create;
  FParser := Parser;
  FMacroValues := MacroValues;
  FParamNames := TStringList.Create;
end;

destructor TLXMacroSQLBuilder.Destroy;
begin
  FParamNames.Free;
  inherited;
end;

procedure TLXMacroSQLBuilder.VisitText(Element: TLXMacroSQLText);
begin
  FSQL := FSQL + Element.Text;
end;

procedure TLXMacroSQLBuilder.VisitParameter(Element: TLXMacroSQLParameter);
begin
  FSQL := FSQL + '?';
  FParamNames.Add(Element.Text);
end;

procedure TLXMacroSQLBuilder.VisitMacro(Element: TLXMacroSQLMacro);
var
  MacroSQLElement: TLXMacroSQLElement;
begin
  MacroSQLElement := FParser.Parse(FMacroValues.Values[Element.Text]);
  try
    MacroSQLElement.Accept(Self);
  finally
    MacroSQLElement.Free;
  end;
end;

procedure TLXMacroSQLBuilder.VisitConditional(Element: TLXMacroSQLConditional);
begin
  Element.Condition.Accept(Self);
  if FBooleanValue then
    Element.ThenElement.Accept(Self)
  else if Element.ElseElement <> nil then
    Element.ElseElement.Accept(Self);
end;

procedure TLXMacroSQLBuilder.VisitError(
  Element: TLXMacroSQLError);
begin
  raise ELXMacroSQLError.Create(Element.Text, Element.Line, Element.Column);
end;

procedure TLXMacroSQLBuilder.VisitLogicalIdentifier(
  Element: TLXMacroSQLLogicalIdentifier);
var
  Index: Integer;
begin
  Index := FMacroValues.IndexOfName(Element.Text);
  if Index >= 0 then
    FStringValue := FMacroValues.Values[FMacroValues.Names[Index]] else
    FStringValue := Element.Text;
end;

procedure TLXMacroSQLBuilder.VisitLogicalExists(Element: TLXMacroSQLLogicalExists);
begin
  FBooleanValue := (FMacroValues.IndexOf(Element.Identifier.Text) >= 0) or
    (FMacroValues.IndexOfName(Element.Identifier.Text) >= 0);
end;

procedure TLXMacroSQLBuilder.VisitLogicalEQUAL(Element: TLXMacroSQLLogicalEQUAL);
var
  LeftOperand: string;
begin
  Element.LeftOperand.Accept(Self);
  LeftOperand := FStringValue;
  Element.RightOperand.Accept(Self);
  FBooleanValue := LowerCase(LeftOperand) = LowerCase(FStringValue);
end;

procedure TLXMacroSQLBuilder.VisitLogicalNOTEQUAL(Element: TLXMacroSQLLogicalNOTEQUAL);
var
  LeftOperand: string;
begin
  Element.LeftOperand.Accept(Self);
  LeftOperand := FStringValue;
  Element.RightOperand.Accept(Self);
  FBooleanValue := LowerCase(LeftOperand) <> LowerCase(FStringValue);
end;

procedure TLXMacroSQLBuilder.VisitLogicalNOT(Element: TLXMacroSQLLogicalNOT);
begin
  Element.Operand.Accept(Self);
  FBooleanValue := not FBooleanValue;
end;

procedure TLXMacroSQLBuilder.VisitLogicalAND(Element: TLXMacroSQLLogicalAND);
var
  LeftOperand: Boolean;
begin
  Element.LeftOperand.Accept(Self);
  LeftOperand := FBooleanValue;
  Element.RightOperand.Accept(Self);
  FBooleanValue := LeftOperand and FBooleanValue;
end;

procedure TLXMacroSQLBuilder.VisitLogicalOR(Element: TLXMacroSQLLogicalOR);
var
  LeftOperand: Boolean;
begin
  Element.LeftOperand.Accept(Self);
  LeftOperand := FBooleanValue;
  Element.RightOperand.Accept(Self);
  FBooleanValue := LeftOperand or FBooleanValue;
end;

function TLXMacroSQLBuilder.SQL: string;
begin
  Result := FSQL;
end;

function TLXMacroSQLBuilder.ParamNames: TStrings;
begin
  Result := FParamNames;
end;


end.